#include "kernel.h"
#include "globals.h"
#include "types.h"
#include "errno.h"

#include "util/string.h"
#include "util/printf.h"
#include "util/debug.h"

#include "fs/dirent.h"
#include "fs/fcntl.h"
#include "fs/stat.h"
#include "fs/vfs.h"
#include "fs/vnode.h"

#define PATH_LENGTH 200
#define MAX_DIRECTORY_DEPTH 100

int PathIfStartFromRoot(const char *path);
int PathGetFileName(const char *path,const char **fileName);
int PathGetPathDepth(const char* path);
void PathGetOffDuplicatedSlash(char *pathWithDupSlash, char *pathNoDupSlash);
void PathGetOffDot(const char* pathWithDot, char* pathNoDot);

int StrIfEmpty(char* str);
void StrGetCharPositions(char* str, char objectChar, int* indexArr, int maxNum);
int StrGetLength(const char* str);
void StrCopy(char* fromStr,const char* toStr, int length);
void StrReplaceChar(char *str,char objChar, char changedChar);


void PathGetOffDuplicatedSlash(char *pathWithDupSlash, char *pathNoDupSlash)
{
	int i,j,flag;
	for(i=0,j=0;pathWithDupSlash[i]!='\0';i++)
	{
		if(pathWithDupSlash[i]=='/')
		{
			if(flag==1)
			{
				continue;
			}
			flag=1;
		}
		else
		{
			flag=0;
		}
		pathNoDupSlash[j]=pathWithDupSlash[i];
		j++;
	}
	pathNoDupSlash[j]='\0';
}

void StrReplaceChar(char *str,char objChar, char changedChar)
{
	int i;
	for(i=0;str[i]!='\0';i++)
	{
		if(str[i]==objChar)
			str[i]=changedChar;
	}
	}
int StrIfEmpty(char* str)
{
	return str[0]=='\0'?1:0;
	}

void StrCopy(char* toStr,const char* fromStr, int length)
{
	int i;
	for(i=0;i<length;i++)
	{
		toStr[i]=fromStr[i];
	}
	}

int StrGetLength(const char* str)
{
	int i=0;
	for(;str[i]!='\0';i++)
	{

	}
	return i;
}

int PathIfDir(const char *path)
{
	int i=0;
	for(i=0;path[i]!='\0';i++)
	{
		if(path[i]=='/')
			return 1;
	}
	return 0;
}

int PathGetPathDepth(const char* path)
{
	int i,depth;
	for(i=1,depth=1;path[i]!='\0';i++)
	{
		if(path[i]=='/'&&path[i+1]!='\0')
		{
			depth++;
		}
	}
	return depth;
	}

void StrGetCharPositions(char* str, char objectChar, int* indexArr, int maxNum)
{
	int i,j;
	for(i=0,j=0;str[i]!='\0'&&j<=maxNum;i++)
	{
		if(str[i]==objectChar)
		{
			indexArr[j]=i;
			j++;
		}
	}
	}

void PathGetOffDot(const char* pathWithDot, char* pathNoDot)
{
	char temptStr[PATH_LENGTH];
	int offset=0;
	if(pathWithDot[0]!='/')
	{
		temptStr[0]='/';
		offset=1;
	}

	StrCopy(temptStr+offset,pathWithDot,PATH_LENGTH);
	int depth,length;
	depth=PathGetPathDepth(pathWithDot);
	length=StrGetLength(pathWithDot);
	int indexArr[MAX_DIRECTORY_DEPTH];

	StrGetCharPositions(temptStr,'/',indexArr,depth);
	StrReplaceChar(temptStr,'/','\0');

	int i=depth-1;
	if(indexArr[depth-1]+1>=length&&depth>1)
	{
		if(temptStr[indexArr[depth-1]+1]=='.')
		{
			temptStr[indexArr[depth-1]+1]='\0';
		}
		i--;
	}

	int jumpOutCount=0;
	for(;i>=0;i--)
	{
		if(temptStr[indexArr[i]+1]=='.')
		{
			if(temptStr[indexArr[i]+2]=='.')
			{
				temptStr[indexArr[i]+1]='\0';
				jumpOutCount++;
				continue;
			}
			else if(temptStr[indexArr[i]+2]=='\0')
			{
				temptStr[indexArr[i]+1]='\0';
				continue;
			}
		}
		if(jumpOutCount>0)
		{
			temptStr[indexArr[i]+1]='\0';
			jumpOutCount--;
		}
	}

	pathNoDot[0]='\0';

	for(i=0;i<depth;i++)
	{
		if(i==0&&pathWithDot[0]!='/')
		{
			sprintf(pathNoDot,"%s",&temptStr[indexArr[i]+1]);
		}

		else if(temptStr[indexArr[i]+1]!='\0')
		{
			sprintf(pathNoDot,"%s/%s",pathNoDot,&temptStr[indexArr[i]+1]);
		}
	}
	}

int PathIfStartFromRoot(const char *path)
{
	return path[0]=='/'?1:0;
}

int PathGetFileName(const char *path,const char **fileName)
{
	dbg(DBG_VFS,"PahtGetFileName path=%s\n",path);
	int pahtLength,i;
	*fileName=path;
	pahtLength=StrGetLength(path);
	for(i=pahtLength;i>=0;i--)
	{
		if(path[i]=='/')
		{
			*fileName=path+i+1;
			break;
		}
	}
	return StrGetLength(*fileName);
}

/* arc/ewr/waer to arc*/
int PathGetNextDirectory(char* path,char* directory)
{
	int i;
	char tempPath[PATH_LENGTH];
	if(StrIfEmpty(path))
		return -1;

	StrCopy(tempPath,path,PATH_LENGTH);
	KASSERT(NULL!=directory);
	for(i=0;tempPath[i]!='\0';i++)
	{
		if(tempPath[i]=='/')
		{
			tempPath[i]='\0';
			StrCopy(directory,tempPath,STR_MAX);
			StrCopy(path,tempPath+i+1,PATH_LENGTH);
			return 1;/*still some*/
		}
	}
	StrCopy(directory,path,STR_MAX);
	*path='\0';
	return 0;/*no any more directory*/
	}
/* This takes a base 'dir', a 'name', its 'len', and a result vnode.
 * Most of the work should be done by the vnode's implementation
 * specific lookup() function, but you may want to special case
 * "." and/or ".." here depnding on your implementation.
 *
 * If dir has no lookup(), return -ENOTDIR.
 *
 * Note: returns with the vnode refcount on *result incremented.
 */
int
lookup(vnode_t *dir, const char *name, size_t len, vnode_t **result)
{
	KASSERT(NULL != dir);
	KASSERT(NULL != name);
	KASSERT(NULL != result);

	if(dir->vn_ops->lookup==NULL)
	{
        return -ENOTDIR;
	}
	if(len>STR_MAX)
	{
		return -ENAMETOOLONG;
	}

        return dir->vn_ops->lookup(dir,name,len,result);
}


/* When successful this function returns data in the following "out"-arguments:
 *  o res_vnode: the vnode of the parent directory of "name"
 *  o name: the `basename' (the element of the pathname)
 *  o namelen: the length of the basename
 *
 * For example: dir_namev("/s5fs/bin/ls", &namelen, &name, NULL,
 * &res_vnode) would put 2 in namelen, "ls" in name, and a pointer to the
 * vnode corresponding to "/s5fs/bin" in res_vnode.
 *
 * The "base" argument defines where we start resolving the path from:
 * A base value of NULL means to use the process's current working directory,
 * curproc->p_cwd.  If pathname[0] == '/', ignore base and start with
 * vfs_root_vn.  dir_namev() should call lookup() to take care of resolving each
 * piece of the pathname.
 *
 * Note: A successful call to this causes vnode refcount on *res_vnode to
 * be incremented.
 */


int
dir_namev(const char *pathname, size_t *namelen, const char **name,
          vnode_t *base, vnode_t **res_vnode)
{
		KASSERT(NULL != pathname);
		KASSERT(NULL != namelen);
		KASSERT(NULL != name);
		KASSERT(NULL != res_vnode);

		if(pathname[0]=='\0')
		{
			return -EINVAL;
		}

		char pathnameCopy[PATH_LENGTH];
		const char *temptPathname;
		temptPathname=pathnameCopy;
		StrCopy(pathnameCopy,pathname,PATH_LENGTH);

		vnode_t *curBase=base;
		if(PathIfStartFromRoot(temptPathname))
		{
			curBase=vfs_root_vn;
			temptPathname=temptPathname+1;
		}
		if(curBase==NULL)
		{
			curBase=curproc->p_cwd;
		}
		*res_vnode=curBase;

		char absolutePath[PATH_LENGTH],restPath[PATH_LENGTH];
		PathGetOffDot(temptPathname,absolutePath);

		PathGetFileName(pathname,name);
		*namelen=StrGetLength(*name);

		char directoryName[STR_MAX];
		int directoryState,directoryLength,lookupCode;
		while((directoryState=PathGetNextDirectory(absolutePath,directoryName))>=0)
		{
			if (!S_ISDIR(curBase->vn_mode))
			{
				return -ENOTDIR;
			}
			if(directoryState>0)
			{
				directoryLength=StrGetLength(directoryName);
				lookupCode=lookup(curBase,directoryName,directoryLength,res_vnode);
				if(lookupCode!=0)
				{
					dbg(DBG_VFS,"GRADING! In dir_namev():lookup err\n");
					return lookupCode;
				}
                		vput(*res_vnode);
                		curBase=*res_vnode;
			}
			else if(directoryState==0)
			{
				 KASSERT(NULL !=res_vnode);


				/* vnode_t **takingUp;

				 vnode_t *temptTestBase=*res_vnode;
				 directoryLength=StrGetLength(directoryName);
				 if(lookup(temptTestBase,directoryName,directoryLength,&takingUp)<0)
				 {
					 return -ENOENT;
				 }*/

				 vget(curBase->vn_fs,curBase->vn_vno);
				 dbg(DBG_VFS,"GRADING! dir_namev() return successfully, find path %s\n", *name);
				 return 0;
			}
		}
	vget(curBase->vn_fs,curBase->vn_vno);
        return 0;
}

/* This returns in res_vnode the vnode requested by the other parameters.
 * It makes use of dir_namev and lookup to find the specified vnode (if it
 * exists).  flag is right out of the parameters to open(2); see
 * <weenix/fnctl.h>.  If the O_CREAT flag is specified, and the file does
 * not exist call create() in the parent directory vnode.
 *
 * Note: Increments vnode refcount on *res_vnode.
 */
int
open_namev(const char *pathname, int flag, vnode_t **res_vnode, vnode_t *base)
{

	size_t nameLen;
	int dirCode,lookupCode;
    const char *fileName;
    vnode_t *directory,*curBase;

    if((dirCode=dir_namev(pathname,&nameLen,&fileName,base,&directory))>=0)
    {
    	lookupCode=lookup(directory,fileName,nameLen,res_vnode);
			
			vput(directory);
    		if(lookupCode==-ENOENT&&flag)
    		{
    			 KASSERT(NULL != directory->vn_ops->create);
    			 directory->vn_ops->create(directory,fileName,nameLen,res_vnode);
    			 return 0;
    		}
    		else if(lookupCode!=0)
    		{
    			dbg(DBG_VFS,"GRADING! dir_namev() finished with errNo");
    		}
    		else if(lookupCode==0)
    		{	
    			dbg(DBG_VFS,"GRADING! dir_namev() finished successfully");
    		}
    		return lookupCode;
    }

    else
    {
    	return dirCode;
    }
}

#ifdef __GETCWD__
/* Finds the name of 'entry' in the directory 'dir'. The name is writen
 * to the given buffer. On success 0 is returned. If 'dir' does not
 * contain 'entry' then -ENOENT is returned. If the given buffer cannot
 * hold the result then it is filled with as many characters as possible
 * and a null terminator, -ERANGE is returned.
 *
 * Files can be uniquely identified within a file system by their
 * inode numbers. */
int
lookup_name(vnode_t *dir, vnode_t *entry, char *buf, size_t size)
{
        NOT_YET_IMPLEMENTED("GETCWD: lookup_name");
        return -ENOENT;
}


/* Used to find the absolute path of the directory 'dir'. Since
 * directories cannot have more than one link there is always
 * a unique solution. The path is writen to the given buffer.
 * On success 0 is returned. On error this function returns a
 * negative error code. See the man page for getcwd(3) for
 * possible errors. Even if an error code is returned the buffer
 * will be filled with a valid string which has some partial
 * information about the wanted path. */
ssize_t
lookup_dirpath(vnode_t *dir, char *buf, size_t osize)
{
        NOT_YET_IMPLEMENTED("GETCWD: lookup_dirpath");

        return -ENOENT;
}
#endif /* __GETCWD__ */
